1   /*
2    * Copyright (c) 2000, 2006, Oracle and/or its affiliates. All rights reserved.
3    * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4    *
5    * This code is free software; you can redistribute it and/or modify it
6    * under the terms of the GNU General Public License version 2 only, as
7    * published by the Free Software Foundation.  Oracle designates this
8    * particular file as subject to the "Classpath" exception as provided
9    * by Oracle in the LICENSE file that accompanied this code.
10   *
11   * This code is distributed in the hope that it will be useful, but WITHOUT
12   * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13   * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14   * version 2 for more details (a copy is included in the LICENSE file that
15   * accompanied this code).
16   *
17   * You should have received a copy of the GNU General Public License version
18   * 2 along with this work; if not, write to the Free Software Foundation,
19   * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20   *
21   * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22   * or visit www.oracle.com if you need additional information or have any
23   * questions.
24   */
25  
26  package com.sun.imageio.plugins.png;
27  
28  import java.awt.Point;
29  import java.awt.Rectangle;
30  import java.awt.color.ColorSpace;
31  import java.awt.image.BufferedImage;
32  import java.awt.image.DataBuffer;
33  import java.awt.image.DataBufferByte;
34  import java.awt.image.DataBufferUShort;
35  import java.awt.image.Raster;
36  import java.awt.image.WritableRaster;
37  import java.io.BufferedInputStream;
38  import java.io.ByteArrayInputStream;
39  import java.io.DataInputStream;
40  import java.io.EOFException;
41  import java.io.InputStream;
42  import java.io.IOException;
43  import java.io.SequenceInputStream;
44  import java.util.ArrayList;
45  import java.util.Arrays;
46  import java.util.Enumeration;
47  import java.util.Iterator;
48  import java.util.zip.Inflater;
49  import java.util.zip.InflaterInputStream;
50  import javax.imageio.IIOException;
51  import javax.imageio.ImageReader;
52  import javax.imageio.ImageReadParam;
53  import javax.imageio.ImageTypeSpecifier;
54  import javax.imageio.metadata.IIOMetadata;
55  import javax.imageio.spi.ImageReaderSpi;
56  import javax.imageio.stream.ImageInputStream;
57  import com.sun.imageio.plugins.common.InputStreamAdapter;
58  import com.sun.imageio.plugins.common.ReaderUtil;
59  import com.sun.imageio.plugins.common.SubImageInputStream;
60  import java.io.ByteArrayOutputStream;
61  import sun.awt.image.ByteInterleavedRaster;
62  
63  class PNGImageDataEnumeration implements Enumeration<InputStream> {
64  
65      boolean firstTime = true;
66      ImageInputStream stream;
67      int length;
68  
69      public PNGImageDataEnumeration(ImageInputStream stream)
70          throws IOException {
71          this.stream = stream;
72          this.length = stream.readInt();
73          int type = stream.readInt(); // skip chunk type
74      }
75  
76      public InputStream nextElement() {
77          try {
78              firstTime = false;
79              ImageInputStream iis = new SubImageInputStream(stream, length);
80              return new InputStreamAdapter(iis);
81          } catch (IOException e) {
82              return null;
83          }
84      }
85  
86      public boolean hasMoreElements() {
87          if (firstTime) {
88              return true;
89          }
90  
91          try {
92              int crc = stream.readInt();
93              this.length = stream.readInt();
94              int type = stream.readInt();
95              if (type == PNGImageReader.IDAT_TYPE) {
96                  return true;
97              } else {
98                  return false;
99              }
100         } catch (IOException e) {
101             return false;
102         }
103     }
104 }
105 
106 public class PNGImageReader extends ImageReader {
107 
108     /*
109      * Note: The following chunk type constants are autogenerated.  Each
110      * one is derived from the ASCII values of its 4-character name.  For
111      * example, IHDR_TYPE is calculated as follows:
112      *            ('I' << 24) | ('H' << 16) | ('D' << 8) | 'R'
113      */
114 
115     // Critical chunks
116     static final int IHDR_TYPE = 0x49484452;
117     static final int PLTE_TYPE = 0x504c5445;
118     static final int IDAT_TYPE = 0x49444154;
119     static final int IEND_TYPE = 0x49454e44;
120 
121     // Ancillary chunks
122     static final int bKGD_TYPE = 0x624b4744;
123     static final int cHRM_TYPE = 0x6348524d;
124     static final int gAMA_TYPE = 0x67414d41;
125     static final int hIST_TYPE = 0x68495354;
126     static final int iCCP_TYPE = 0x69434350;
127     static final int iTXt_TYPE = 0x69545874;
128     static final int pHYs_TYPE = 0x70485973;
129     static final int sBIT_TYPE = 0x73424954;
130     static final int sPLT_TYPE = 0x73504c54;
131     static final int sRGB_TYPE = 0x73524742;
132     static final int tEXt_TYPE = 0x74455874;
133     static final int tIME_TYPE = 0x74494d45;
134     static final int tRNS_TYPE = 0x74524e53;
135     static final int zTXt_TYPE = 0x7a545874;
136 
137     static final int PNG_COLOR_GRAY = 0;
138     static final int PNG_COLOR_RGB = 2;
139     static final int PNG_COLOR_PALETTE = 3;
140     static final int PNG_COLOR_GRAY_ALPHA = 4;
141     static final int PNG_COLOR_RGB_ALPHA = 6;
142 
143     // The number of bands by PNG color type
144     static final int[] inputBandsForColorType = {
145          1, // gray
146         -1, // unused
147          3, // rgb
148          1, // palette
149          2, // gray + alpha
150         -1, // unused
151          4  // rgb + alpha
152     };
153 
154     static final int PNG_FILTER_NONE = 0;
155     static final int PNG_FILTER_SUB = 1;
156     static final int PNG_FILTER_UP = 2;
157     static final int PNG_FILTER_AVERAGE = 3;
158     static final int PNG_FILTER_PAETH = 4;
159 
160     static final int[] adam7XOffset = { 0, 4, 0, 2, 0, 1, 0 };
161     static final int[] adam7YOffset = { 0, 0, 4, 0, 2, 0, 1 };
162     static final int[] adam7XSubsampling = { 8, 8, 4, 4, 2, 2, 1, 1 };
163     static final int[] adam7YSubsampling = { 8, 8, 8, 4, 4, 2, 2, 1 };
164 
165     private static final boolean debug = true;
166 
167     ImageInputStream stream = null;
168 
169     boolean gotHeader = false;
170     boolean gotMetadata = false;
171 
172     ImageReadParam lastParam = null;
173 
174     long imageStartPosition = -1L;
175 
176     Rectangle sourceRegion = null;
177     int sourceXSubsampling = -1;
178     int sourceYSubsampling = -1;
179     int sourceMinProgressivePass = 0;
180     int sourceMaxProgressivePass = 6;
181     int[] sourceBands = null;
182     int[] destinationBands = null;
183     Point destinationOffset = new Point(0, 0);
184 
185     PNGMetadata metadata = new PNGMetadata();
186 
187     DataInputStream pixelStream = null;
188 
189     BufferedImage theImage = null;
190 
191     // The number of source pixels processed
192     int pixelsDone = 0;
193 
194     // The total number of pixels in the source image
195     int totalPixels;
196 
197     public PNGImageReader(ImageReaderSpi originatingProvider) {
198         super(originatingProvider);
199     }
200 
201     public void setInput(Object input,
202                          boolean seekForwardOnly,
203                          boolean ignoreMetadata) {
204         super.setInput(input, seekForwardOnly, ignoreMetadata);
205         this.stream = (ImageInputStream)input; // Always works
206 
207         // Clear all values based on the previous stream contents
208         resetStreamSettings();
209     }
210 
211     private String readNullTerminatedString(String charset, int maxLen) throws IOException {
212         ByteArrayOutputStream baos = new ByteArrayOutputStream();
213         int b;
214         int count = 0;
215         while ((maxLen > count++) && ((b = stream.read()) != 0)) {
216             if (b == -1) throw new EOFException();
217             baos.write(b);
218         }
219         return new String(baos.toByteArray(), charset);
220     }
221 
222     private void readHeader() throws IIOException {
223         if (gotHeader) {
224             return;
225         }
226         if (stream == null) {
227             throw new IllegalStateException("Input source not set!");
228         }
229 
230         try {
231             byte[] signature = new byte[8];
232             stream.readFully(signature);
233 
234             if (signature[0] != (byte)137 ||
235                 signature[1] != (byte)80 ||
236                 signature[2] != (byte)78 ||
237                 signature[3] != (byte)71 ||
238                 signature[4] != (byte)13 ||
239                 signature[5] != (byte)10 ||
240                 signature[6] != (byte)26 ||
241                 signature[7] != (byte)10) {
242                 throw new IIOException("Bad PNG signature!");
243             }
244 
245             int IHDR_length = stream.readInt();
246             if (IHDR_length != 13) {
247                 throw new IIOException("Bad length for IHDR chunk!");
248             }
249             int IHDR_type = stream.readInt();
250             if (IHDR_type != IHDR_TYPE) {
251                 throw new IIOException("Bad type for IHDR chunk!");
252             }
253 
254             this.metadata = new PNGMetadata();
255 
256             int width = stream.readInt();
257             int height = stream.readInt();
258 
259             // Re-use signature array to bulk-read these unsigned byte values
260             stream.readFully(signature, 0, 5);
261             int bitDepth          = signature[0] & 0xff;
262             int colorType         = signature[1] & 0xff;
263             int compressionMethod = signature[2] & 0xff;
264             int filterMethod      = signature[3] & 0xff;
265             int interlaceMethod   = signature[4] & 0xff;
266 
267             // Skip IHDR CRC
268             stream.skipBytes(4);
269 
270             stream.flushBefore(stream.getStreamPosition());
271 
272             if (width == 0) {
273                 throw new IIOException("Image width == 0!");
274             }
275             if (height == 0) {
276                 throw new IIOException("Image height == 0!");
277             }
278             if (bitDepth != 1 && bitDepth != 2 && bitDepth != 4 &&
279                 bitDepth != 8 && bitDepth != 16) {
280                 throw new IIOException("Bit depth must be 1, 2, 4, 8, or 16!");
281             }
282             if (colorType != 0 && colorType != 2 && colorType != 3 &&
283                 colorType != 4 && colorType != 6) {
284                 throw new IIOException("Color type must be 0, 2, 3, 4, or 6!");
285             }
286             if (colorType == PNG_COLOR_PALETTE && bitDepth == 16) {
287                 throw new IIOException("Bad color type/bit depth combination!");
288             }
289             if ((colorType == PNG_COLOR_RGB ||
290                  colorType == PNG_COLOR_RGB_ALPHA ||
291                  colorType == PNG_COLOR_GRAY_ALPHA) &&
292                 (bitDepth != 8 && bitDepth != 16)) {
293                 throw new IIOException("Bad color type/bit depth combination!");
294             }
295             if (compressionMethod != 0) {
296                 throw new IIOException("Unknown compression method (not 0)!");
297             }
298             if (filterMethod != 0) {
299                 throw new IIOException("Unknown filter method (not 0)!");
300             }
301             if (interlaceMethod != 0 && interlaceMethod != 1) {
302                 throw new IIOException("Unknown interlace method (not 0 or 1)!");
303             }
304 
305             metadata.IHDR_present = true;
306             metadata.IHDR_width = width;
307             metadata.IHDR_height = height;
308             metadata.IHDR_bitDepth = bitDepth;
309             metadata.IHDR_colorType = colorType;
310             metadata.IHDR_compressionMethod = compressionMethod;
311             metadata.IHDR_filterMethod = filterMethod;
312             metadata.IHDR_interlaceMethod = interlaceMethod;
313             gotHeader = true;
314         } catch (IOException e) {
315             throw new IIOException("I/O error reading PNG header!", e);
316         }
317     }
318 
319     private void parse_PLTE_chunk(int chunkLength) throws IOException {
320         if (metadata.PLTE_present) {
321             processWarningOccurred(
322 "A PNG image may not contain more than one PLTE chunk.\n" +
323 "The chunk wil be ignored.");
324             return;
325         } else if (metadata.IHDR_colorType == PNG_COLOR_GRAY ||
326                    metadata.IHDR_colorType == PNG_COLOR_GRAY_ALPHA) {
327             processWarningOccurred(
328 "A PNG gray or gray alpha image cannot have a PLTE chunk.\n" +
329 "The chunk wil be ignored.");
330             return;
331         }
332 
333         byte[] palette = new byte[chunkLength];
334         stream.readFully(palette);
335 
336         int numEntries = chunkLength/3;
337         if (metadata.IHDR_colorType == PNG_COLOR_PALETTE) {
338             int maxEntries = 1 << metadata.IHDR_bitDepth;
339             if (numEntries > maxEntries) {
340                 processWarningOccurred(
341 "PLTE chunk contains too many entries for bit depth, ignoring extras.");
342                 numEntries = maxEntries;
343             }
344             numEntries = Math.min(numEntries, maxEntries);
345         }
346 
347         // Round array sizes up to 2^2^n
348         int paletteEntries;
349         if (numEntries > 16) {
350             paletteEntries = 256;
351         } else if (numEntries > 4) {
352             paletteEntries = 16;
353         } else if (numEntries > 2) {
354             paletteEntries = 4;
355         } else {
356             paletteEntries = 2;
357         }
358 
359         metadata.PLTE_present = true;
360         metadata.PLTE_red = new byte[paletteEntries];
361         metadata.PLTE_green = new byte[paletteEntries];
362         metadata.PLTE_blue = new byte[paletteEntries];
363 
364         int index = 0;
365         for (int i = 0; i < numEntries; i++) {
366             metadata.PLTE_red[i] = palette[index++];
367             metadata.PLTE_green[i] = palette[index++];
368             metadata.PLTE_blue[i] = palette[index++];
369         }
370     }
371 
372     private void parse_bKGD_chunk() throws IOException {
373         if (metadata.IHDR_colorType == PNG_COLOR_PALETTE) {
374             metadata.bKGD_colorType = PNG_COLOR_PALETTE;
375             metadata.bKGD_index = stream.readUnsignedByte();
376         } else if (metadata.IHDR_colorType == PNG_COLOR_GRAY ||
377                    metadata.IHDR_colorType == PNG_COLOR_GRAY_ALPHA) {
378             metadata.bKGD_colorType = PNG_COLOR_GRAY;
379             metadata.bKGD_gray = stream.readUnsignedShort();
380         } else { // RGB or RGB_ALPHA
381             metadata.bKGD_colorType = PNG_COLOR_RGB;
382             metadata.bKGD_red = stream.readUnsignedShort();
383             metadata.bKGD_green = stream.readUnsignedShort();
384             metadata.bKGD_blue = stream.readUnsignedShort();
385         }
386 
387         metadata.bKGD_present = true;
388     }
389 
390     private void parse_cHRM_chunk() throws IOException {
391         metadata.cHRM_whitePointX = stream.readInt();
392         metadata.cHRM_whitePointY = stream.readInt();
393         metadata.cHRM_redX = stream.readInt();
394         metadata.cHRM_redY = stream.readInt();
395         metadata.cHRM_greenX = stream.readInt();
396         metadata.cHRM_greenY = stream.readInt();
397         metadata.cHRM_blueX = stream.readInt();
398         metadata.cHRM_blueY = stream.readInt();
399 
400         metadata.cHRM_present = true;
401     }
402 
403     private void parse_gAMA_chunk() throws IOException {
404         int gamma = stream.readInt();
405         metadata.gAMA_gamma = gamma;
406 
407         metadata.gAMA_present = true;
408     }
409 
410     private void parse_hIST_chunk(int chunkLength) throws IOException,
411         IIOException
412     {
413         if (!metadata.PLTE_present) {
414             throw new IIOException("hIST chunk without prior PLTE chunk!");
415         }
416 
417         /* According to PNG specification length of
418          * hIST chunk is specified in bytes and
419          * hIST chunk consists of 2 byte elements
420          * (so we expect length is even).
421          */
422         metadata.hIST_histogram = new char[chunkLength/2];
423         stream.readFully(metadata.hIST_histogram,
424                          0, metadata.hIST_histogram.length);
425 
426         metadata.hIST_present = true;
427     }
428 
429     private void parse_iCCP_chunk(int chunkLength) throws IOException {
430         String keyword = readNullTerminatedString("ISO-8859-1", 80);
431         metadata.iCCP_profileName = keyword;
432 
433         metadata.iCCP_compressionMethod = stream.readUnsignedByte();
434 
435         byte[] compressedProfile =
436           new byte[chunkLength - keyword.length() - 2];
437         stream.readFully(compressedProfile);
438         metadata.iCCP_compressedProfile = compressedProfile;
439 
440         metadata.iCCP_present = true;
441     }
442 
443     private void parse_iTXt_chunk(int chunkLength) throws IOException {
444         long chunkStart = stream.getStreamPosition();
445 
446         String keyword = readNullTerminatedString("ISO-8859-1", 80);
447         metadata.iTXt_keyword.add(keyword);
448 
449         int compressionFlag = stream.readUnsignedByte();
450         metadata.iTXt_compressionFlag.add(Boolean.valueOf(compressionFlag == 1));
451 
452         int compressionMethod = stream.readUnsignedByte();
453         metadata.iTXt_compressionMethod.add(Integer.valueOf(compressionMethod));
454 
455         String languageTag = readNullTerminatedString("UTF8", 80);
456         metadata.iTXt_languageTag.add(languageTag);
457 
458         long pos = stream.getStreamPosition();
459         int maxLen = (int)(chunkStart + chunkLength - pos);
460         String translatedKeyword =
461             readNullTerminatedString("UTF8", maxLen);
462         metadata.iTXt_translatedKeyword.add(translatedKeyword);
463 
464         String text;
465         pos = stream.getStreamPosition();
466         byte[] b = new byte[(int)(chunkStart + chunkLength - pos)];
467         stream.readFully(b);
468 
469         if (compressionFlag == 1) { // Decompress the text
470             text = new String(inflate(b), "UTF8");
471         } else {
472             text = new String(b, "UTF8");
473         }
474         metadata.iTXt_text.add(text);
475     }
476 
477     private void parse_pHYs_chunk() throws IOException {
478         metadata.pHYs_pixelsPerUnitXAxis = stream.readInt();
479         metadata.pHYs_pixelsPerUnitYAxis = stream.readInt();
480         metadata.pHYs_unitSpecifier = stream.readUnsignedByte();
481 
482         metadata.pHYs_present = true;
483     }
484 
485     private void parse_sBIT_chunk() throws IOException {
486         int colorType = metadata.IHDR_colorType;
487         if (colorType == PNG_COLOR_GRAY ||
488             colorType == PNG_COLOR_GRAY_ALPHA) {
489             metadata.sBIT_grayBits = stream.readUnsignedByte();
490         } else if (colorType == PNG_COLOR_RGB ||
491                    colorType == PNG_COLOR_PALETTE ||
492                    colorType == PNG_COLOR_RGB_ALPHA) {
493             metadata.sBIT_redBits = stream.readUnsignedByte();
494             metadata.sBIT_greenBits = stream.readUnsignedByte();
495             metadata.sBIT_blueBits = stream.readUnsignedByte();
496         }
497 
498         if (colorType == PNG_COLOR_GRAY_ALPHA ||
499             colorType == PNG_COLOR_RGB_ALPHA) {
500             metadata.sBIT_alphaBits = stream.readUnsignedByte();
501         }
502 
503         metadata.sBIT_colorType = colorType;
504         metadata.sBIT_present = true;
505     }
506 
507     private void parse_sPLT_chunk(int chunkLength)
508         throws IOException, IIOException {
509         metadata.sPLT_paletteName = readNullTerminatedString("ISO-8859-1", 80);
510         chunkLength -= metadata.sPLT_paletteName.length() + 1;
511 
512         int sampleDepth = stream.readUnsignedByte();
513         metadata.sPLT_sampleDepth = sampleDepth;
514 
515         int numEntries = chunkLength/(4*(sampleDepth/8) + 2);
516         metadata.sPLT_red = new int[numEntries];
517         metadata.sPLT_green = new int[numEntries];
518         metadata.sPLT_blue = new int[numEntries];
519         metadata.sPLT_alpha = new int[numEntries];
520         metadata.sPLT_frequency = new int[numEntries];
521 
522         if (sampleDepth == 8) {
523             for (int i = 0; i < numEntries; i++) {
524                 metadata.sPLT_red[i] = stream.readUnsignedByte();
525                 metadata.sPLT_green[i] = stream.readUnsignedByte();
526                 metadata.sPLT_blue[i] = stream.readUnsignedByte();
527                 metadata.sPLT_alpha[i] = stream.readUnsignedByte();
528                 metadata.sPLT_frequency[i] = stream.readUnsignedShort();
529             }
530         } else if (sampleDepth == 16) {
531             for (int i = 0; i < numEntries; i++) {
532                 metadata.sPLT_red[i] = stream.readUnsignedShort();
533                 metadata.sPLT_green[i] = stream.readUnsignedShort();
534                 metadata.sPLT_blue[i] = stream.readUnsignedShort();
535                 metadata.sPLT_alpha[i] = stream.readUnsignedShort();
536                 metadata.sPLT_frequency[i] = stream.readUnsignedShort();
537             }
538         } else {
539             throw new IIOException("sPLT sample depth not 8 or 16!");
540         }
541 
542         metadata.sPLT_present = true;
543     }
544 
545     private void parse_sRGB_chunk() throws IOException {
546         metadata.sRGB_renderingIntent = stream.readUnsignedByte();
547 
548         metadata.sRGB_present = true;
549     }
550 
551     private void parse_tEXt_chunk(int chunkLength) throws IOException {
552         String keyword = readNullTerminatedString("ISO-8859-1", 80);
553         metadata.tEXt_keyword.add(keyword);
554 
555         byte[] b = new byte[chunkLength - keyword.length() - 1];
556         stream.readFully(b);
557         metadata.tEXt_text.add(new String(b, "ISO-8859-1"));
558     }
559 
560     private void parse_tIME_chunk() throws IOException {
561         metadata.tIME_year = stream.readUnsignedShort();
562         metadata.tIME_month = stream.readUnsignedByte();
563         metadata.tIME_day = stream.readUnsignedByte();
564         metadata.tIME_hour = stream.readUnsignedByte();
565         metadata.tIME_minute = stream.readUnsignedByte();
566         metadata.tIME_second = stream.readUnsignedByte();
567 
568         metadata.tIME_present = true;
569     }
570 
571     private void parse_tRNS_chunk(int chunkLength) throws IOException {
572         int colorType = metadata.IHDR_colorType;
573         if (colorType == PNG_COLOR_PALETTE) {
574             if (!metadata.PLTE_present) {
575                 processWarningOccurred(
576 "tRNS chunk without prior PLTE chunk, ignoring it.");
577                 return;
578             }
579 
580             // Alpha table may have fewer entries than RGB palette
581             int maxEntries = metadata.PLTE_red.length;
582             int numEntries = chunkLength;
583             if (numEntries > maxEntries) {
584                 processWarningOccurred(
585 "tRNS chunk has more entries than prior PLTE chunk, ignoring extras.");
586                 numEntries = maxEntries;
587             }
588             metadata.tRNS_alpha = new byte[numEntries];
589             metadata.tRNS_colorType = PNG_COLOR_PALETTE;
590             stream.read(metadata.tRNS_alpha, 0, numEntries);
591             stream.skipBytes(chunkLength - numEntries);
592         } else if (colorType == PNG_COLOR_GRAY) {
593             if (chunkLength != 2) {
594                 processWarningOccurred(
595 "tRNS chunk for gray image must have length 2, ignoring chunk.");
596                 stream.skipBytes(chunkLength);
597                 return;
598             }
599             metadata.tRNS_gray = stream.readUnsignedShort();
600             metadata.tRNS_colorType = PNG_COLOR_GRAY;
601         } else if (colorType == PNG_COLOR_RGB) {
602             if (chunkLength != 6) {
603                 processWarningOccurred(
604 "tRNS chunk for RGB image must have length 6, ignoring chunk.");
605                 stream.skipBytes(chunkLength);
606                 return;
607             }
608             metadata.tRNS_red = stream.readUnsignedShort();
609             metadata.tRNS_green = stream.readUnsignedShort();
610             metadata.tRNS_blue = stream.readUnsignedShort();
611             metadata.tRNS_colorType = PNG_COLOR_RGB;
612         } else {
613             processWarningOccurred(
614 "Gray+Alpha and RGBS images may not have a tRNS chunk, ignoring it.");
615             return;
616         }
617 
618         metadata.tRNS_present = true;
619     }
620 
621     private static byte[] inflate(byte[] b) throws IOException {
622         InputStream bais = new ByteArrayInputStream(b);
623         InputStream iis = new InflaterInputStream(bais);
624         ByteArrayOutputStream baos = new ByteArrayOutputStream();
625 
626         int c;
627         try {
628             while ((c = iis.read()) != -1) {
629                 baos.write(c);
630             }
631         } finally {
632             iis.close();
633         }
634         return baos.toByteArray();
635     }
636 
637     private void parse_zTXt_chunk(int chunkLength) throws IOException {
638         String keyword = readNullTerminatedString("ISO-8859-1", 80);
639         metadata.zTXt_keyword.add(keyword);
640 
641         int method = stream.readUnsignedByte();
642         metadata.zTXt_compressionMethod.add(new Integer(method));
643 
644         byte[] b = new byte[chunkLength - keyword.length() - 2];
645         stream.readFully(b);
646         metadata.zTXt_text.add(new String(inflate(b), "ISO-8859-1"));
647     }
648 
649     private void readMetadata() throws IIOException {
650         if (gotMetadata) {
651             return;
652         }
653 
654         readHeader();
655 
656         /*
657          * Optimization: We can skip the remaining metadata if the
658          * ignoreMetadata flag is set, and only if this is not a palette
659          * image (in that case, we need to read the metadata to get the
660          * tRNS chunk, which is needed for the getImageTypes() method).
661          */
662         int colorType = metadata.IHDR_colorType;
663         if (ignoreMetadata && colorType != PNG_COLOR_PALETTE) {
664             try {
665                 while (true) {
666                     int chunkLength = stream.readInt();
667                     int chunkType = stream.readInt();
668 
669                     if (chunkType == IDAT_TYPE) {
670                         // We've reached the image data
671                         stream.skipBytes(-8);
672                         imageStartPosition = stream.getStreamPosition();
673                         break;
674                     } else {
675                         // Skip the chunk plus the 4 CRC bytes that follow
676                         stream.skipBytes(chunkLength + 4);
677                     }
678                 }
679             } catch (IOException e) {
680                 throw new IIOException("Error skipping PNG metadata", e);
681             }
682 
683             gotMetadata = true;
684             return;
685         }
686 
687         try {
688             loop: while (true) {
689                 int chunkLength = stream.readInt();
690                 int chunkType = stream.readInt();
691 
692                 switch (chunkType) {
693                 case IDAT_TYPE:
694                     // If chunk type is 'IDAT', we've reached the image data.
695                     stream.skipBytes(-8);
696                     imageStartPosition = stream.getStreamPosition();
697                     break loop;
698                 case PLTE_TYPE:
699                     parse_PLTE_chunk(chunkLength);
700                     break;
701                 case bKGD_TYPE:
702                     parse_bKGD_chunk();
703                     break;
704                 case cHRM_TYPE:
705                     parse_cHRM_chunk();
706                     break;
707                 case gAMA_TYPE:
708                     parse_gAMA_chunk();
709                     break;
710                 case hIST_TYPE:
711                     parse_hIST_chunk(chunkLength);
712                     break;
713                 case iCCP_TYPE:
714                     parse_iCCP_chunk(chunkLength);
715                     break;
716                 case iTXt_TYPE:
717                     parse_iTXt_chunk(chunkLength);
718                     break;
719                 case pHYs_TYPE:
720                     parse_pHYs_chunk();
721                     break;
722                 case sBIT_TYPE:
723                     parse_sBIT_chunk();
724                     break;
725                 case sPLT_TYPE:
726                     parse_sPLT_chunk(chunkLength);
727                     break;
728                 case sRGB_TYPE:
729                     parse_sRGB_chunk();
730                     break;
731                 case tEXt_TYPE:
732                     parse_tEXt_chunk(chunkLength);
733                     break;
734                 case tIME_TYPE:
735                     parse_tIME_chunk();
736                     break;
737                 case tRNS_TYPE:
738                     parse_tRNS_chunk(chunkLength);
739                     break;
740                 case zTXt_TYPE:
741                     parse_zTXt_chunk(chunkLength);
742                     break;
743                 default:
744                     // Read an unknown chunk
745                     byte[] b = new byte[chunkLength];
746                     stream.readFully(b);
747 
748                     StringBuilder chunkName = new StringBuilder(4);
749                     chunkName.append((char)(chunkType >>> 24));
750                     chunkName.append((char)((chunkType >> 16) & 0xff));
751                     chunkName.append((char)((chunkType >> 8) & 0xff));
752                     chunkName.append((char)(chunkType & 0xff));
753 
754                     int ancillaryBit = chunkType >>> 28;
755                     if (ancillaryBit == 0) {
756                         processWarningOccurred(
757 "Encountered unknown chunk with critical bit set!");
758                     }
759 
760                     metadata.unknownChunkType.add(chunkName.toString());
761                     metadata.unknownChunkData.add(b);
762                     break;
763                 }
764 
765                 int chunkCRC = stream.readInt();
766                 stream.flushBefore(stream.getStreamPosition());
767             }
768         } catch (IOException e) {
769             throw new IIOException("Error reading PNG metadata", e);
770         }
771 
772         gotMetadata = true;
773     }
774 
775     // Data filtering methods
776 
777     private static void decodeSubFilter(byte[] curr, int coff, int count,
778                                         int bpp) {
779         for (int i = bpp; i < count; i++) {
780             int val;
781 
782             val = curr[i + coff] & 0xff;
783             val += curr[i + coff - bpp] & 0xff;
784 
785             curr[i + coff] = (byte)val;
786         }
787     }
788 
789     private static void decodeUpFilter(byte[] curr, int coff,
790                                        byte[] prev, int poff,
791                                        int count) {
792         for (int i = 0; i < count; i++) {
793             int raw = curr[i + coff] & 0xff;
794             int prior = prev[i + poff] & 0xff;
795 
796             curr[i + coff] = (byte)(raw + prior);
797         }
798     }
799 
800     private static void decodeAverageFilter(byte[] curr, int coff,
801                                             byte[] prev, int poff,
802                                             int count, int bpp) {
803         int raw, priorPixel, priorRow;
804 
805         for (int i = 0; i < bpp; i++) {
806             raw = curr[i + coff] & 0xff;
807             priorRow = prev[i + poff] & 0xff;
808 
809             curr[i + coff] = (byte)(raw + priorRow/2);
810         }
811 
812         for (int i = bpp; i < count; i++) {
813             raw = curr[i + coff] & 0xff;
814             priorPixel = curr[i + coff - bpp] & 0xff;
815             priorRow = prev[i + poff] & 0xff;
816 
817             curr[i + coff] = (byte)(raw + (priorPixel + priorRow)/2);
818         }
819     }
820 
821     private static int paethPredictor(int a, int b, int c) {
822         int p = a + b - c;
823         int pa = Math.abs(p - a);
824         int pb = Math.abs(p - b);
825         int pc = Math.abs(p - c);
826 
827         if ((pa <= pb) && (pa <= pc)) {
828             return a;
829         } else if (pb <= pc) {
830             return b;
831         } else {
832             return c;
833         }
834     }
835 
836     private static void decodePaethFilter(byte[] curr, int coff,
837                                           byte[] prev, int poff,
838                                           int count, int bpp) {
839         int raw, priorPixel, priorRow, priorRowPixel;
840 
841         for (int i = 0; i < bpp; i++) {
842             raw = curr[i + coff] & 0xff;
843             priorRow = prev[i + poff] & 0xff;
844 
845             curr[i + coff] = (byte)(raw + priorRow);
846         }
847 
848         for (int i = bpp; i < count; i++) {
849             raw = curr[i + coff] & 0xff;
850             priorPixel = curr[i + coff - bpp] & 0xff;
851             priorRow = prev[i + poff] & 0xff;
852             priorRowPixel = prev[i + poff - bpp] & 0xff;
853 
854             curr[i + coff] = (byte)(raw + paethPredictor(priorPixel,
855                                                          priorRow,
856                                                          priorRowPixel));
857         }
858     }
859 
860     private static final int[][] bandOffsets = {
861         null,
862         { 0 }, // G
863         { 0, 1 }, // GA in GA order
864         { 0, 1, 2 }, // RGB in RGB order
865         { 0, 1, 2, 3 } // RGBA in RGBA order
866     };
867 
868     private WritableRaster createRaster(int width, int height, int bands,
869                                         int scanlineStride,
870                                         int bitDepth) {
871 
872         DataBuffer dataBuffer;
873         WritableRaster ras = null;
874         Point origin = new Point(0, 0);
875         if ((bitDepth < 8) && (bands == 1)) {
876             dataBuffer = new DataBufferByte(height*scanlineStride);
877             ras = Raster.createPackedRaster(dataBuffer,
878                                             width, height,
879                                             bitDepth,
880                                             origin);
881         } else if (bitDepth <= 8) {
882             dataBuffer = new DataBufferByte(height*scanlineStride);
883             ras = Raster.createInterleavedRaster(dataBuffer,
884                                                  width, height,
885                                                  scanlineStride,
886                                                  bands,
887                                                  bandOffsets[bands],
888                                                  origin);
889         } else {
890             dataBuffer = new DataBufferUShort(height*scanlineStride);
891             ras = Raster.createInterleavedRaster(dataBuffer,
892                                                  width, height,
893                                                  scanlineStride,
894                                                  bands,
895                                                  bandOffsets[bands],
896                                                  origin);
897         }
898 
899         return ras;
900     }
901 
902     private void skipPass(int passWidth, int passHeight)
903         throws IOException, IIOException  {
904         if ((passWidth == 0) || (passHeight == 0)) {
905             return;
906         }
907 
908         int inputBands = inputBandsForColorType[metadata.IHDR_colorType];
909         int bytesPerRow = (inputBands*passWidth*metadata.IHDR_bitDepth + 7)/8;
910 
911         // Read the image row-by-row
912         for (int srcY = 0; srcY < passHeight; srcY++) {
913             // Skip filter byte and the remaining row bytes
914             pixelStream.skipBytes(1 + bytesPerRow);
915 
916             // If read has been aborted, just return
917             // processReadAborted will be called later
918             if (abortRequested()) {
919                 return;
920             }
921         }
922     }
923 
924     private void updateImageProgress(int newPixels) {
925         pixelsDone += newPixels;
926         processImageProgress(100.0F*pixelsDone/totalPixels);
927     }
928 
929     private void decodePass(int passNum,
930                             int xStart, int yStart,
931                             int xStep, int yStep,
932                             int passWidth, int passHeight) throws IOException {
933 
934         if ((passWidth == 0) || (passHeight == 0)) {
935             return;
936         }
937 
938         WritableRaster imRas = theImage.getWritableTile(0, 0);
939         int dstMinX = imRas.getMinX();
940         int dstMaxX = dstMinX + imRas.getWidth() - 1;
941         int dstMinY = imRas.getMinY();
942         int dstMaxY = dstMinY + imRas.getHeight() - 1;
943 
944         // Determine which pixels will be updated in this pass
945         int[] vals =
946           ReaderUtil.computeUpdatedPixels(sourceRegion,
947                                           destinationOffset,
948                                           dstMinX, dstMinY,
949                                           dstMaxX, dstMaxY,
950                                           sourceXSubsampling,
951                                           sourceYSubsampling,
952                                           xStart, yStart,
953                                           passWidth, passHeight,
954                                           xStep, yStep);
955         int updateMinX = vals[0];
956         int updateMinY = vals[1];
957         int updateWidth = vals[2];
958         int updateXStep = vals[4];
959         int updateYStep = vals[5];
960 
961         int bitDepth = metadata.IHDR_bitDepth;
962         int inputBands = inputBandsForColorType[metadata.IHDR_colorType];
963         int bytesPerPixel = (bitDepth == 16) ? 2 : 1;
964         bytesPerPixel *= inputBands;
965 
966         int bytesPerRow = (inputBands*passWidth*bitDepth + 7)/8;
967         int eltsPerRow = (bitDepth == 16) ? bytesPerRow/2 : bytesPerRow;
968 
969         // If no pixels need updating, just skip the input data
970         if (updateWidth == 0) {
971             for (int srcY = 0; srcY < passHeight; srcY++) {
972                 // Update count of pixels read
973                 updateImageProgress(passWidth);
974                 // Skip filter byte and the remaining row bytes
975                 pixelStream.skipBytes(1 + bytesPerRow);
976             }
977             return;
978         }
979 
980         // Backwards map from destination pixels
981         // (dstX = updateMinX + k*updateXStep)
982         // to source pixels (sourceX), and then
983         // to offset and skip in passRow (srcX and srcXStep)
984         int sourceX =
985             (updateMinX - destinationOffset.x)*sourceXSubsampling +
986             sourceRegion.x;
987         int srcX = (sourceX - xStart)/xStep;
988 
989         // Compute the step factor in the source
990         int srcXStep = updateXStep*sourceXSubsampling/xStep;
991 
992         byte[] byteData = null;
993         short[] shortData = null;
994         byte[] curr = new byte[bytesPerRow];
995         byte[] prior = new byte[bytesPerRow];
996 
997         // Create a 1-row tall Raster to hold the data
998         WritableRaster passRow = createRaster(passWidth, 1, inputBands,
999                                               eltsPerRow,
1000                                               bitDepth);
1001 
1002         // Create an array suitable for holding one pixel
1003         int[] ps = passRow.getPixel(0, 0, (int[])null);
1004 
1005         DataBuffer dataBuffer = passRow.getDataBuffer();
1006         int type = dataBuffer.getDataType();
1007         if (type == DataBuffer.TYPE_BYTE) {
1008             byteData = ((DataBufferByte)dataBuffer).getData();
1009         } else {
1010             shortData = ((DataBufferUShort)dataBuffer).getData();
1011         }
1012 
1013         processPassStarted(theImage,
1014                            passNum,
1015                            sourceMinProgressivePass,
1016                            sourceMaxProgressivePass,
1017                            updateMinX, updateMinY,
1018                            updateXStep, updateYStep,
1019                            destinationBands);
1020 
1021         // Handle source and destination bands
1022         if (sourceBands != null) {
1023             passRow = passRow.createWritableChild(0, 0,
1024                                                   passRow.getWidth(), 1,
1025                                                   0, 0,
1026                                                   sourceBands);
1027         }
1028         if (destinationBands != null) {
1029             imRas = imRas.createWritableChild(0, 0,
1030                                               imRas.getWidth(),
1031                                               imRas.getHeight(),
1032                                               0, 0,
1033                                               destinationBands);
1034         }
1035 
1036         // Determine if all of the relevant output bands have the
1037         // same bit depth as the source data
1038         boolean adjustBitDepths = false;
1039         int[] outputSampleSize = imRas.getSampleModel().getSampleSize();
1040         int numBands = outputSampleSize.length;
1041         for (int b = 0; b < numBands; b++) {
1042             if (outputSampleSize[b] != bitDepth) {
1043                 adjustBitDepths = true;
1044                 break;
1045             }
1046         }
1047 
1048         // If the bit depths differ, create a lookup table per band to perform
1049         // the conversion
1050         int[][] scale = null;
1051         if (adjustBitDepths) {
1052             int maxInSample = (1 << bitDepth) - 1;
1053             int halfMaxInSample = maxInSample/2;
1054             scale = new int[numBands][];
1055             for (int b = 0; b < numBands; b++) {
1056                 int maxOutSample = (1 << outputSampleSize[b]) - 1;
1057                 scale[b] = new int[maxInSample + 1];
1058                 for (int s = 0; s <= maxInSample; s++) {
1059                     scale[b][s] =
1060                         (s*maxOutSample + halfMaxInSample)/maxInSample;
1061                 }
1062             }
1063         }
1064 
1065         // Limit passRow to relevant area for the case where we
1066         // will can setRect to copy a contiguous span
1067         boolean useSetRect = srcXStep == 1 &&
1068             updateXStep == 1 &&
1069             !adjustBitDepths &&
1070             (imRas instanceof ByteInterleavedRaster);
1071 
1072         if (useSetRect) {
1073             passRow = passRow.createWritableChild(srcX, 0,
1074                                                   updateWidth, 1,
1075                                                   0, 0,
1076                                                   null);
1077         }
1078 
1079         // Decode the (sub)image row-by-row
1080         for (int srcY = 0; srcY < passHeight; srcY++) {
1081             // Update count of pixels read
1082             updateImageProgress(passWidth);
1083 
1084             // Read the filter type byte and a row of data
1085             int filter = pixelStream.read();
1086             try {
1087                 // Swap curr and prior
1088                 byte[] tmp = prior;
1089                 prior = curr;
1090                 curr = tmp;
1091 
1092                 pixelStream.readFully(curr, 0, bytesPerRow);
1093             } catch (java.util.zip.ZipException ze) {
1094                 // TODO - throw a more meaningful exception
1095                 throw ze;
1096             }
1097 
1098             switch (filter) {
1099             case PNG_FILTER_NONE:
1100                 break;
1101             case PNG_FILTER_SUB:
1102                 decodeSubFilter(curr, 0, bytesPerRow, bytesPerPixel);
1103                 break;
1104             case PNG_FILTER_UP:
1105                 decodeUpFilter(curr, 0, prior, 0, bytesPerRow);
1106                 break;
1107             case PNG_FILTER_AVERAGE:
1108                 decodeAverageFilter(curr, 0, prior, 0, bytesPerRow,
1109                                     bytesPerPixel);
1110                 break;
1111             case PNG_FILTER_PAETH:
1112                 decodePaethFilter(curr, 0, prior, 0, bytesPerRow,
1113                                   bytesPerPixel);
1114                 break;
1115             default:
1116                 throw new IIOException("Unknown row filter type (= " +
1117                                        filter + ")!");
1118             }
1119 
1120             // Copy data into passRow byte by byte
1121             if (bitDepth < 16) {
1122                 System.arraycopy(curr, 0, byteData, 0, bytesPerRow);
1123             } else {
1124                 int idx = 0;
1125                 for (int j = 0; j < eltsPerRow; j++) {
1126                     shortData[j] =
1127                         (short)((curr[idx] << 8) | (curr[idx + 1] & 0xff));
1128                     idx += 2;
1129                 }
1130             }
1131 
1132             // True Y position in source
1133             int sourceY = srcY*yStep + yStart;
1134             if ((sourceY >= sourceRegion.y) &&
1135                 (sourceY < sourceRegion.y + sourceRegion.height) &&
1136                 (((sourceY - sourceRegion.y) %
1137                   sourceYSubsampling) == 0)) {
1138 
1139                 int dstY = destinationOffset.y +
1140                     (sourceY - sourceRegion.y)/sourceYSubsampling;
1141                 if (dstY < dstMinY) {
1142                     continue;
1143                 }
1144                 if (dstY > dstMaxY) {
1145                     break;
1146                 }
1147 
1148                 if (useSetRect) {
1149                     imRas.setRect(updateMinX, dstY, passRow);
1150                 } else {
1151                     int newSrcX = srcX;
1152 
1153                     for (int dstX = updateMinX;
1154                          dstX < updateMinX + updateWidth;
1155                          dstX += updateXStep) {
1156 
1157                         passRow.getPixel(newSrcX, 0, ps);
1158                         if (adjustBitDepths) {
1159                             for (int b = 0; b < numBands; b++) {
1160                                 ps[b] = scale[b][ps[b]];
1161                             }
1162                         }
1163                         imRas.setPixel(dstX, dstY, ps);
1164                         newSrcX += srcXStep;
1165                     }
1166                 }
1167 
1168                 processImageUpdate(theImage,
1169                                    updateMinX, dstY,
1170                                    updateWidth, 1,
1171                                    updateXStep, updateYStep,
1172                                    destinationBands);
1173 
1174                 // If read has been aborted, just return
1175                 // processReadAborted will be called later
1176                 if (abortRequested()) {
1177                     return;
1178                 }
1179             }
1180         }
1181 
1182         processPassComplete(theImage);
1183     }
1184 
1185     private void decodeImage()
1186         throws IOException, IIOException  {
1187         int width = metadata.IHDR_width;
1188         int height = metadata.IHDR_height;
1189 
1190         this.pixelsDone = 0;
1191         this.totalPixels = width*height;
1192 
1193         clearAbortRequest();
1194 
1195         if (metadata.IHDR_interlaceMethod == 0) {
1196             decodePass(0, 0, 0, 1, 1, width, height);
1197         } else {
1198             for (int i = 0; i <= sourceMaxProgressivePass; i++) {
1199                 int XOffset = adam7XOffset[i];
1200                 int YOffset = adam7YOffset[i];
1201                 int XSubsampling = adam7XSubsampling[i];
1202                 int YSubsampling = adam7YSubsampling[i];
1203                 int xbump = adam7XSubsampling[i + 1] - 1;
1204                 int ybump = adam7YSubsampling[i + 1] - 1;
1205 
1206                 if (i >= sourceMinProgressivePass) {
1207                     decodePass(i,
1208                                XOffset,
1209                                YOffset,
1210                                XSubsampling,
1211                                YSubsampling,
1212                                (width + xbump)/XSubsampling,
1213                                (height + ybump)/YSubsampling);
1214                 } else {
1215                     skipPass((width + xbump)/XSubsampling,
1216                              (height + ybump)/YSubsampling);
1217                 }
1218 
1219                 // If read has been aborted, just return
1220                 // processReadAborted will be called later
1221                 if (abortRequested()) {
1222                     return;
1223                 }
1224             }
1225         }
1226     }
1227 
1228     private void readImage(ImageReadParam param) throws IIOException {
1229         readMetadata();
1230 
1231         int width = metadata.IHDR_width;
1232         int height = metadata.IHDR_height;
1233 
1234         // Init default values
1235         sourceXSubsampling = 1;
1236         sourceYSubsampling = 1;
1237         sourceMinProgressivePass = 0;
1238         sourceMaxProgressivePass = 6;
1239         sourceBands = null;
1240         destinationBands = null;
1241         destinationOffset = new Point(0, 0);
1242 
1243         // If an ImageReadParam is available, get values from it
1244         if (param != null) {
1245             sourceXSubsampling = param.getSourceXSubsampling();
1246             sourceYSubsampling = param.getSourceYSubsampling();
1247 
1248             sourceMinProgressivePass =
1249                 Math.max(param.getSourceMinProgressivePass(), 0);
1250             sourceMaxProgressivePass =
1251                 Math.min(param.getSourceMaxProgressivePass(), 6);
1252 
1253             sourceBands = param.getSourceBands();
1254             destinationBands = param.getDestinationBands();
1255             destinationOffset = param.getDestinationOffset();
1256         }
1257         Inflater inf = null;
1258         try {
1259             stream.seek(imageStartPosition);
1260 
1261             Enumeration<InputStream> e = new PNGImageDataEnumeration(stream);
1262             InputStream is = new SequenceInputStream(e);
1263 
1264            /* InflaterInputStream uses an Inflater instance which consumes
1265             * native (non-GC visible) resources. This is normally implicitly
1266             * freed when the stream is closed. However since the
1267             * InflaterInputStream wraps a client-supplied input stream,
1268             * we cannot close it.
1269             * But the app may depend on GC finalization to close the stream.
1270             * Therefore to ensure timely freeing of native resources we
1271             * explicitly create the Inflater instance and free its resources
1272             * when we are done with the InflaterInputStream by calling
1273             * inf.end();
1274             */
1275             inf = new Inflater();
1276             is = new InflaterInputStream(is, inf);
1277             is = new BufferedInputStream(is);
1278             this.pixelStream = new DataInputStream(is);
1279 
1280             theImage = getDestination(param,
1281                                       getImageTypes(0),
1282                                       width,
1283                                       height);
1284 
1285             Rectangle destRegion = new Rectangle(0, 0, 0, 0);
1286             sourceRegion = new Rectangle(0, 0, 0, 0);
1287             computeRegions(param, width, height,
1288                            theImage,
1289                            sourceRegion, destRegion);
1290             destinationOffset.setLocation(destRegion.getLocation());
1291 
1292             // At this point the header has been read and we know
1293             // how many bands are in the image, so perform checking
1294             // of the read param.
1295             int colorType = metadata.IHDR_colorType;
1296             checkReadParamBandSettings(param,
1297                                        inputBandsForColorType[colorType],
1298                                       theImage.getSampleModel().getNumBands());
1299 
1300             processImageStarted(0);
1301             decodeImage();
1302             if (abortRequested()) {
1303                 processReadAborted();
1304             } else {
1305                 processImageComplete();
1306             }
1307         } catch (IOException e) {
1308             throw new IIOException("Error reading PNG image data", e);
1309         } finally {
1310             if (inf != null) {
1311                 inf.end();
1312             }
1313         }
1314     }
1315 
1316     public int getNumImages(boolean allowSearch) throws IIOException {
1317         if (stream == null) {
1318             throw new IllegalStateException("No input source set!");
1319         }
1320         if (seekForwardOnly && allowSearch) {
1321             throw new IllegalStateException
1322                 ("seekForwardOnly and allowSearch can't both be true!");
1323         }
1324         return 1;
1325     }
1326 
1327     public int getWidth(int imageIndex) throws IIOException {
1328         if (imageIndex != 0) {
1329             throw new IndexOutOfBoundsException("imageIndex != 0!");
1330         }
1331 
1332         readHeader();
1333 
1334         return metadata.IHDR_width;
1335     }
1336 
1337     public int getHeight(int imageIndex) throws IIOException {
1338         if (imageIndex != 0) {
1339             throw new IndexOutOfBoundsException("imageIndex != 0!");
1340         }
1341 
1342         readHeader();
1343 
1344         return metadata.IHDR_height;
1345     }
1346 
1347     public Iterator<ImageTypeSpecifier> getImageTypes(int imageIndex)
1348       throws IIOException
1349     {
1350         if (imageIndex != 0) {
1351             throw new IndexOutOfBoundsException("imageIndex != 0!");
1352         }
1353 
1354         readHeader();
1355 
1356         ArrayList<ImageTypeSpecifier> l =
1357             new ArrayList<ImageTypeSpecifier>(1);
1358 
1359         ColorSpace rgb;
1360         ColorSpace gray;
1361         int[] bandOffsets;
1362 
1363         int bitDepth = metadata.IHDR_bitDepth;
1364         int colorType = metadata.IHDR_colorType;
1365 
1366         int dataType;
1367         if (bitDepth <= 8) {
1368             dataType = DataBuffer.TYPE_BYTE;
1369         } else {
1370             dataType = DataBuffer.TYPE_USHORT;
1371         }
1372 
1373         switch (colorType) {
1374         case PNG_COLOR_GRAY:
1375             // Packed grayscale
1376             l.add(ImageTypeSpecifier.createGrayscale(bitDepth,
1377                                                      dataType,
1378                                                      false));
1379             break;
1380 
1381         case PNG_COLOR_RGB:
1382             if (bitDepth == 8) {
1383                 // some standard types of buffered images
1384                 // which can be used as destination
1385                 l.add(ImageTypeSpecifier.createFromBufferedImageType(
1386                           BufferedImage.TYPE_3BYTE_BGR));
1387 
1388                 l.add(ImageTypeSpecifier.createFromBufferedImageType(
1389                           BufferedImage.TYPE_INT_RGB));
1390 
1391                 l.add(ImageTypeSpecifier.createFromBufferedImageType(
1392                           BufferedImage.TYPE_INT_BGR));
1393 
1394             }
1395             // Component R, G, B
1396             rgb = ColorSpace.getInstance(ColorSpace.CS_sRGB);
1397             bandOffsets = new int[3];
1398             bandOffsets[0] = 0;
1399             bandOffsets[1] = 1;
1400             bandOffsets[2] = 2;
1401             l.add(ImageTypeSpecifier.createInterleaved(rgb,
1402                                                        bandOffsets,
1403                                                        dataType,
1404                                                        false,
1405                                                        false));
1406             break;
1407 
1408         case PNG_COLOR_PALETTE:
1409             readMetadata(); // Need tRNS chunk
1410 
1411             /*
1412              * The PLTE chunk spec says:
1413              *
1414              * The number of palette entries must not exceed the range that
1415              * can be represented in the image bit depth (for example, 2^4 = 16
1416              * for a bit depth of 4). It is permissible to have fewer entries
1417              * than the bit depth would allow. In that case, any out-of-range
1418              * pixel value found in the image data is an error.
1419              *
1420              * http://www.libpng.org/pub/png/spec/1.2/PNG-Chunks.html#C.PLTE
1421              *
1422              * Consequently, the case when the palette length is smaller than
1423              * 2^bitDepth is legal in the view of PNG spec.
1424              *
1425              * However the spec of createIndexed() method demands the exact
1426              * equality of the palette lengh and number of possible palette
1427              * entries (2^bitDepth).
1428              *
1429              * {@link javax.imageio.ImageTypeSpecifier.html#createIndexed}
1430              *
1431              * In order to avoid this contradiction we need to extend the
1432              * palette arrays to the limit defined by the bitDepth.
1433              */
1434 
1435             int plength = 1 << bitDepth;
1436 
1437             byte[] red = metadata.PLTE_red;
1438             byte[] green = metadata.PLTE_green;
1439             byte[] blue = metadata.PLTE_blue;
1440 
1441             if (metadata.PLTE_red.length < plength) {
1442                 red = Arrays.copyOf(metadata.PLTE_red, plength);
1443                 Arrays.fill(red, metadata.PLTE_red.length, plength,
1444                             metadata.PLTE_red[metadata.PLTE_red.length - 1]);
1445 
1446                 green = Arrays.copyOf(metadata.PLTE_green, plength);
1447                 Arrays.fill(green, metadata.PLTE_green.length, plength,
1448                             metadata.PLTE_green[metadata.PLTE_green.length - 1]);
1449 
1450                 blue = Arrays.copyOf(metadata.PLTE_blue, plength);
1451                 Arrays.fill(blue, metadata.PLTE_blue.length, plength,
1452                             metadata.PLTE_blue[metadata.PLTE_blue.length - 1]);
1453 
1454             }
1455 
1456             // Alpha from tRNS chunk may have fewer entries than
1457             // the RGB LUTs from the PLTE chunk; if so, pad with
1458             // 255.
1459             byte[] alpha = null;
1460             if (metadata.tRNS_present && (metadata.tRNS_alpha != null)) {
1461                 if (metadata.tRNS_alpha.length == red.length) {
1462                     alpha = metadata.tRNS_alpha;
1463                 } else {
1464                     alpha = Arrays.copyOf(metadata.tRNS_alpha, red.length);
1465                     Arrays.fill(alpha,
1466                                 metadata.tRNS_alpha.length,
1467                                 red.length, (byte)255);
1468                 }
1469             }
1470 
1471             l.add(ImageTypeSpecifier.createIndexed(red, green,
1472                                                    blue, alpha,
1473                                                    bitDepth,
1474                                                    DataBuffer.TYPE_BYTE));
1475             break;
1476 
1477         case PNG_COLOR_GRAY_ALPHA:
1478             // Component G, A
1479             gray = ColorSpace.getInstance(ColorSpace.CS_GRAY);
1480             bandOffsets = new int[2];
1481             bandOffsets[0] = 0;
1482             bandOffsets[1] = 1;
1483             l.add(ImageTypeSpecifier.createInterleaved(gray,
1484                                                        bandOffsets,
1485                                                        dataType,
1486                                                        true,
1487                                                        false));
1488             break;
1489 
1490         case PNG_COLOR_RGB_ALPHA:
1491             if (bitDepth == 8) {
1492                 // some standard types of buffered images
1493                 // wich can be used as destination
1494                 l.add(ImageTypeSpecifier.createFromBufferedImageType(
1495                           BufferedImage.TYPE_4BYTE_ABGR));
1496 
1497                 l.add(ImageTypeSpecifier.createFromBufferedImageType(
1498                           BufferedImage.TYPE_INT_ARGB));
1499             }
1500 
1501             // Component R, G, B, A (non-premultiplied)
1502             rgb = ColorSpace.getInstance(ColorSpace.CS_sRGB);
1503             bandOffsets = new int[4];
1504             bandOffsets[0] = 0;
1505             bandOffsets[1] = 1;
1506             bandOffsets[2] = 2;
1507             bandOffsets[3] = 3;
1508 
1509             l.add(ImageTypeSpecifier.createInterleaved(rgb,
1510                                                        bandOffsets,
1511                                                        dataType,
1512                                                        true,
1513                                                        false));
1514             break;
1515 
1516         default:
1517             break;
1518         }
1519 
1520         return l.iterator();
1521     }
1522 
1523     /*
1524      * Super class implementation uses first element
1525      * of image types list as raw image type.
1526      *
1527      * Also, super implementation uses first element of this list
1528      * as default destination type image read param does not specify
1529      * anything other.
1530      *
1531      * However, in case of RGB and RGBA color types, raw image type
1532      * produces buffered image of custom type. It causes some
1533      * performance degradation of subsequent rendering operations.
1534      *
1535      * To resolve this contradiction we put standard image types
1536      * at the first positions of image types list (to produce standard
1537      * images by default) and put raw image type (which is custom)
1538      * at the last position of this list.
1539      *
1540      * After this changes we should override getRawImageType()
1541      * to return last element of image types list.
1542      */
1543     public ImageTypeSpecifier getRawImageType(int imageIndex)
1544       throws IOException {
1545 
1546         Iterator<ImageTypeSpecifier> types = getImageTypes(imageIndex);
1547         ImageTypeSpecifier raw = null;
1548         do {
1549             raw = types.next();
1550         } while (types.hasNext());
1551         return raw;
1552     }
1553 
1554     public ImageReadParam getDefaultReadParam() {
1555         return new ImageReadParam();
1556     }
1557 
1558     public IIOMetadata getStreamMetadata()
1559         throws IIOException {
1560         return null;
1561     }
1562 
1563     public IIOMetadata getImageMetadata(int imageIndex) throws IIOException {
1564         if (imageIndex != 0) {
1565             throw new IndexOutOfBoundsException("imageIndex != 0!");
1566         }
1567         readMetadata();
1568         return metadata;
1569     }
1570 
1571     public BufferedImage read(int imageIndex, ImageReadParam param)
1572         throws IIOException {
1573         if (imageIndex != 0) {
1574             throw new IndexOutOfBoundsException("imageIndex != 0!");
1575         }
1576 
1577         readImage(param);
1578         return theImage;
1579     }
1580 
1581     public void reset() {
1582         super.reset();
1583         resetStreamSettings();
1584     }
1585 
1586     private void resetStreamSettings() {
1587         gotHeader = false;
1588         gotMetadata = false;
1589         metadata = null;
1590         pixelStream = null;
1591     }
1592 }